/**
 * @file lv_port_disp_templ.c
 *
 */

/*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_disp.h"

#include "bsp_init.h"

/* SWM341 LVGL

## 移植说明

绘制缓冲区是 LVGL 用来渲染屏幕内容的简单数组。一旦渲染准备就绪，绘制缓冲区的内容将使用显示驱动程序中设置的flush_cb函数发送到显示器。

如您所见，绘制缓冲区可以小于屏幕。在这种情况下，较大的区域将被重新绘制为适合绘制缓冲区的较小部分。 如果只有一个小区域发生变化（例如按下按钮），则只会刷新该区域。

更大的缓冲区会导致更好的性能，但超过 1/10 屏幕大小的缓冲区没有显着的性能改进。 因此，建议选择绘制缓冲区的大小至少为屏幕大小的 1/10。

LVGL提供了三种配置缓冲区的方式，我们都对其进行移植。

- lv_port_disp1.c

只使用一个缓冲区，LVGL 将屏幕内容绘制到该绘制缓冲区中并将其发送到显示器。 这样 LVGL 需要等到缓冲区的内容发送到显示器，然后再在其中绘制新内容。

- lv_port_disp2_1.c

使用两个缓冲区，LVGL 可以绘制到一个缓冲区中，而另一个缓冲区的内容被发送到后台显示。 应使用 DMA2D 将数据传输到显示器，让 MCU 同时绘制。 这样，显示的渲染和刷新变得并行。

- lv_port_disp2_2.c

使用两个缓冲区，LVGL 可以绘制到一个缓冲区中，而另一个缓冲区的内容被发送到后台显示。 应使用 DMA 将数据传输到显示器，让 MCU 同时绘制。 这样，显示的渲染和刷新变得并行。

- lv_port_disp3.c

提供 2 个屏幕大小的绘制缓冲区，LVGL 的显示处理就像“传统”双缓冲一样工作。

这意味着在 flush_cb 中只有帧缓冲区的地址需要更改为提供的指针（color_p 参数）。 如果 MCU 具有 LCD 控制器外围设备而不是外部显示控制器（例如 ILI9341 或 SSD1963），则应使用此配置。

您可以使用 基准示例 测量不同绘制缓冲区配置的性能。
*/

/*********************
 *      DEFINES
 *********************/
/**
 * 根据用户实际应用需要来选择显示缓冲配置 (理论上效率 3 > 2 ~ 1 > 0)
 * 0  - lv_port_disp1
 * 1  - lv_port_disp2_1
 * 2  - lv_port_disp2_2
 * 3  - lv_port_disp3
 * 注意 : lv_port_disp2_2 使用了 DMA, 不能让 DAC 同时使用 DMA.
 * 因为 DMA 配置为外部 trigger 信号启动时, DMA 其他通道不工作, 此问题只能尽量避让绕开应用(即 DAC 独占 DMA or No use DAC_DMA )
 * 所以实际应用中, 一般都使用 lv_disp3 , 在此也强烈推荐使用 disp_3 , 其他 disp_1/2 仅用作参考.
 *
 * 此外 disp_3 不支持 LVGL 内建的软件旋转.
 */
#define LVGL_DISP_MODE      3

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void disp_init(void);

static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
#if LV_USE_GPU
static void gpu_blend(lv_disp_drv_t *disp_drv, lv_color_t *dest, const lv_color_t *src, uint32_t length, lv_opa_t opa);
static void gpu_fill(lv_disp_drv_t *disp_drv, lv_color_t *dest_buf, lv_coord_t dest_width,
                     const lv_area_t *fill_area, lv_color_t color);
#endif

/**********************
 *  STATIC VARIABLES
 **********************/
/** Descriptor of a display driver */
static lv_disp_drv_t disp_drv;
/** A screen sized buffer */
static lv_color_t lcdbuf_gui[LV_HOR_RES_MAX * LV_VER_RES_MAX] __attribute__((section(".SDRAM1")));

#if (1 == LVGL_DISP_MODE)

#elif (2 == LVGL_DISP_MODE)
static DMA_InitStructure DMA_initStruct;

static volatile int16_t x1_flush;
static volatile int16_t y1_flush;
static volatile int16_t x2_flush;
static volatile int16_t y2_flush;
static volatile int16_t y_flush_act;
static volatile const lv_color_t *buf_to_flush;

#else
/** An other screen sized buffer */
static lv_color_t lcdbuf_show[LV_HOR_RES_MAX * LV_VER_RES_MAX] __attribute__((section(".SDRAM1")));
#endif

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();
    /*-----------------------------
     * Create a buffer for drawing
     *----------------------------*/
    /* LVGL requires a buffer where it internally draws the widgets.
     * Later this buffer will passed your display drivers `flush_cb` to copy its content to your dispay.
     * The buffer has to be greater than 1 display row
     *
     * There are three buffering configurations:
     * 1. Create ONE buffer with some rows:
     *      LVGL will draw the display's content here and writes it to your display
     *
     * 2. Create TWO buffer with some rows:
     *      LVGL will draw the display's content to a buffer and writes it your display.
     *      You should use DMA to write the buffer's content to the display.
     *      It will enable LVGL to draw the next part of the screen to the other buffer while
     *      the data is being sent form the first buffer. It makes rendering and flushing parallel.
     *
     * 3. Create TWO screen-sized buffer:
     *      Similar to 2) but the buffer have to be screen sized. When LVGL is ready it will give the
     *      whole frame to display. This way you only need to change the frame buffer's address instead of
     *      copying the pixels.
     * */
    #if (0 == LVGL_DISP_MODE)
    /* Example for 1) */
    static lv_disp_buf_t draw_buf_dsc;
    static lv_color_t draw_buf_1[LV_HOR_RES_MAX * 10] __attribute__((section(".SDRAM1"))); //A buffer for 10 rows
    lv_disp_buf_init(&draw_buf_dsc, draw_buf_1, NULL, LV_HOR_RES_MAX * 10);   //Initialize the display buffer
    #elif (1 == LVGL_DISP_MODE || 2 == LVGL_DISP_MODE)
    /* Example for 2) */
    static lv_disp_buf_t draw_buf_dsc;
    static lv_color_t draw_buf_2_1[LV_HOR_RES_MAX * 10] __attribute__((section(".SDRAM1"))); //A buffer for 10 rows
    static lv_color_t draw_buf_2_2[LV_HOR_RES_MAX * 10] __attribute__((section(".SDRAM1"))); //An other buffer for 10 rows
    lv_disp_buf_init(&draw_buf_dsc, draw_buf_2_1, draw_buf_2_2, LV_HOR_RES_MAX * 10); //Initialize the display buffer
    #elif (3 == LVGL_DISP_MODE)
    /* Example for 3) */
    static lv_disp_buf_t draw_buf_dsc;
    lv_disp_buf_init(&draw_buf_dsc, lcdbuf_gui, lcdbuf_show, LV_HOR_RES_MAX * LV_VER_RES_MAX); //Initialize the display buffer
    #endif
    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/
    //  lv_disp_drv_t disp_drv;      /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv); /*Basic initialization*/
    /*Set up the functions to access to your display*/
    //  disp_drv.sw_rotate = 1;
    //  disp_drv.rotated = LV_DISP_ROT_90;// lv_disp_3 不支持 LVGL 软件旋转
    /*Set the resolution of the display*/
    disp_drv.hor_res = LV_HOR_RES_MAX;
    disp_drv.ver_res = LV_VER_RES_MAX;
    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;
    /*Set a display buffer*/
    disp_drv.buffer = &draw_buf_dsc;
    #if LV_USE_GPU
    /*Optionally add functions to access the GPU. (Only in buffered mode, LV_VDB_SIZE != 0)*/
    /*Blend two color array using opacity*/
    disp_drv.gpu_blend_cb = gpu_blend;
    /*Fill a memory array with a color*/
    disp_drv.gpu_fill_cb = gpu_fill;
    #endif
    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
/* Initialize your display and the required peripherals. */
static void disp_init(void)
{
    /* You code here */
    lcd_init();
    LCD->L[LCD_LAYER_1].ADDR = (uint32_t)lcdbuf_gui; /* 指向 LCD 显存区域 */
    if (0 != (LCD->CR & LCD_CR_AUTORESTA_Msk))
    {
        /* 软件启动 LCD (仅使能 LCD 中断后有效) */
        LCD_Start(LCD);
    }
    #if  (0 == LVGL_DISP_MODE || 1 == LVGL_DISP_MODE || 2 == LVGL_DISP_MODE)
    /* 若在 lv_disp_1 下配置 LCD 双缓冲, 则需要在 lv_task_handler() 被调用处后添加 void gui2show(void) 函数调用 */
    //  LCD->L[LCD_LAYER_1].ADDR = (uint32_t)lcdbuf_show;
    #if (1 == LVGL_DISP_MODE)
    DMA2D_InitStructure DMA2D_initStruct;
    DMA2D_initStruct.Interval = CyclesPerUs;
    DMA2D_initStruct.IntEOTEn = 1;
    DMA2D_Init(&DMA2D_initStruct);
    DMA2D->L[DMA2D_LAYER_FG].OR  = 0;
    #if (LV_COLOR_DEPTH == 16)
    DMA2D->L[DMA2D_LAYER_FG].PFCCR = (DMA2D_FMT_RGB565 << DMA2D_PFCCR_CFMT_Pos);
    #endif
    #if (LV_COLOR_DEPTH == 32)
    DMA2D->L[DMA2D_LAYER_FG].PFCCR = (DMA2D_FMT_ARGB888 << DMA2D_PFCCR_CFMT_Pos);
    #endif
    #endif
    #endif
}


#if  (0 == LVGL_DISP_MODE)
/* 若在 lv_disp_1 下使用 LCD 双缓冲时, 该函数必须跟随 lv_task_handler() 一起被调用 */
void gui2show(void)
{
    //    uint32_t t_start = swm_gettick();
    memcpy(lcdbuf_show, lcdbuf_gui, sizeof(lcdbuf_show));
    //    printf("systick = [%d]\r\n", swm_gettick() - t_start);
}

/**
 * @brief   软件描点
 */
static inline void put_px(int16_t x, int16_t y, lv_color_t color)
{
    lcdbuf_gui[y * LV_HOR_RES_MAX + x] = color;
}

#elif (1 == LVGL_DISP_MODE)
/**
 * @brief   DMA2D ISR Callback
 */
void isr_dma2d(void)
{
    DMA2D->IF = DMA2D_IF_DONE_Msk;//DMA2D_INTClr();
    lv_disp_flush_ready(&disp_drv);
}

#elif (2 == LVGL_DISP_MODE)
/**
 * @brief   DMA_CH(LCD) ISR Callback
 */
void isr_dma_lcd(void)
{
    if (DMA_CH_INTStat(DMA_CH1, DMA_IT_DONE))
    {
        DMA_CH_INTClr(DMA_CH1, DMA_IT_DONE);
        y_flush_act++;
        if (y_flush_act > y2_flush)
        {
            lv_disp_flush_ready(&disp_drv);
        }
        else
        {
            buf_to_flush += (x2_flush - x1_flush + 1);
            DMA->CH[DMA_CH1].SRC = (uint32_t)buf_to_flush;
            DMA->CH[DMA_CH1].DST = (uint32_t)&lcdbuf_gui[LV_HOR_RES_MAX * y_flush_act + x1_flush];
            DMA->CH[DMA_CH1].CR |= (1 << DMA_CR_RXEN_Pos);
        }
    }
}
#endif

/* Flush the content of the internal buffer the specific area on the display
 * You can use DMA or any hardware acceleration to do this operation in the background but
 * 'lv_disp_flush_ready()' has to be called when finished. */
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
    #if (0 == LVGL_DISP_MODE)
    int32_t x;
    int32_t y;
    for (y = area->y1; y <= area->y2; y++)
    {
        for (x = area->x1; x <= area->x2; x++)
        {
            // Put a pixel to the display. For example:
            put_px(x, y, *color_p);
            color_p++;
        }
    }
    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
    #elif (1 == LVGL_DISP_MODE)
    DMA2D->L[DMA2D_LAYER_FG].MAR = (uint32_t)color_p;
    DMA2D->L[DMA2D_LAYER_OUT].MAR = (uint32_t)&lcdbuf_gui[LV_HOR_RES_MAX * area->y1 + area->x1];
    DMA2D->L[DMA2D_LAYER_OUT].OR  = LV_HOR_RES_MAX - (area->x2 - area->x1 + 1);
    DMA2D->NLR = ((area->y2 - area->y1) << DMA2D_NLR_NLINE_Pos) |
                 ((area->x2 - area->x1) << DMA2D_NLR_NPIXEL_Pos);
    DMA2D->CR &= ~DMA2D_CR_MODE_Msk;
    DMA2D->CR |= (0 << DMA2D_CR_MODE_Pos) |
                 (1 << DMA2D_CR_START_Pos);
    #elif (2 == LVGL_DISP_MODE)
    x1_flush = area->x1;
    x2_flush = area->x2;
    y1_flush = area->y1;
    y2_flush = area->y2;
    y_flush_act = area->y1;
    buf_to_flush = color_p;
    DMA_initStruct.Mode = DMA_MODE_SINGLE;
    #if (LV_COLOR_DEPTH == 16)
    DMA_initStruct.Unit = DMA_UNIT_HALFWORD;
    #endif
    #if (LV_COLOR_DEPTH == 32)
    DMA_initStruct.Unit = DMA_UNIT_WORD;
    #endif
    DMA_initStruct.Count = x2_flush - x1_flush + 1;
    DMA_initStruct.SrcAddr = (uint32_t)buf_to_flush;
    DMA_initStruct.SrcAddrInc = 1;
    DMA_initStruct.DstAddr = (uint32_t)&lcdbuf_gui[LV_HOR_RES_MAX * y_flush_act + x1_flush];
    DMA_initStruct.DstAddrInc = 1;
    DMA_initStruct.Handshake = DMA_HS_NO;
    DMA_initStruct.Priority = DMA_PRI_LOW;
    DMA_initStruct.INTEn = DMA_IT_DONE;
    DMA_CH_Init(DMA_CH1, &DMA_initStruct);
    DMA_CH_Open(DMA_CH1);
    #elif (3 == LVGL_DISP_MODE)
    LCD->L[LCD_LAYER_1].ADDR = (uint32_t)color_p;
    LCD->CR |= (1 << LCD_CR_VBPRELOAD_Pos);
    /* 等待 reload 完成生效后, 硬件自动清零 */
    while (0 != (LCD->CR & LCD_CR_VBPRELOAD_Msk))
        __NOP();
    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
    #endif
}

/*OPTIONAL: GPU INTERFACE*/
#if LV_USE_GPU

/* If your MCU has hardware accelerator (GPU) then you can use it to blend to memories using opacity
 * It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/
static void gpu_blend(lv_disp_drv_t *disp_drv, lv_color_t *dest, const lv_color_t *src, uint32_t length, lv_opa_t opa)
{
    /*It's an example code which should be done by your GPU*/
    uint32_t i;
    for (i = 0; i < length; i++)
    {
        dest[i] = lv_color_mix(dest[i], src[i], opa);
    }
}

/* If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color
 * It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/
static void gpu_fill(lv_disp_drv_t *disp_drv, lv_color_t *dest_buf, lv_coord_t dest_width,
                     const lv_area_t *fill_area, lv_color_t color)
{
    /*It's an example code which should be done by your GPU*/
    int32_t x, y;
    dest_buf += dest_width * fill_area->y1; /*Go to the first line*/
    for (y = fill_area->y1; y <= fill_area->y2; y++)
    {
        for (x = fill_area->x1; x <= fill_area->x2; x++)
        {
            dest_buf[x] = color;
        }
        dest_buf += dest_width; /*Go to the next line*/
    }
}

#endif /*LV_USE_GPU*/

#else /* Enable this file at the top */

/* This dummy typedef exists purely to silence -Wpedantic. */
typedef int keep_pedantic_happy;
#endif
